/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.ui.wizards.service;

import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.NONE;
import static java.awt.GridBagConstraints.WEST;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.text.JTextComponent;

import org.isqlviewer.sql.JdbcService;
import org.isqlviewer.swing.SwingUtilities;
import org.isqlviewer.ui.wizards.AbstractWizardStep;
import org.isqlviewer.ui.wizards.WizardContext;
import org.isqlviewer.util.LocalMessages;

/**
 * Wizard step for configuring JDBC driver options.
 * <p>
 * Options that are configured with this step are usually platform specific, and depends on what the driver is built to
 * support. Things like connecting to the database over SSL can *usually* be configured here as in PostgreSQL.
 * <p>
 * Often Drivers will allow all of these options provided in this step to be embedded in the JDBC URL itself.
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class DriverPropertyOptions extends AbstractWizardStep implements ActionListener {

    private LocalMessages messages = new LocalMessages(ServiceWizard.BUNDLE_NAME);
    private DriverPropertyInfo[] currentOptions = new DriverPropertyInfo[0];
    private Container uiContainer = null;
    private Hashtable<String, JComponent> componentMap = new Hashtable<String, JComponent>();
    private JCheckBox enableOptions = new JCheckBox();
    private Driver driverRefrence;

    private static final String JDBC_ENV_PRINCIPAL = "user";
    private static final String JDBC_ENV_PASSWORD = "password";

    public void actionPerformed(ActionEvent event) {

        Object src = event.getSource();
        String command = event.getActionCommand();
        WizardContext context = getContext();

        if (src == enableOptions) {
            activate(context);
        } else if ("refresh".equals(command)) {
            Properties properties = (Properties) context.getAttribute(ServiceWizard.ATTRIBUTE_PROPERTIES);
            Set<Map.Entry<String, JComponent>> entries = componentMap.entrySet();
            for (Map.Entry<String, JComponent> entry : entries) {
                String newValue = getPropertyValue(entry.getValue());
                if (newValue != null) {
                    properties.setProperty(entry.getKey(), newValue);
                } else {
                    properties.remove(entry.getKey());
                }
            }
            activate(context);
        } else if ("revert".equals(command)) {
            context.removeAttribute(ServiceWizard.ATTRIBUTE_PROPERTIES);
            context.setAttribute(ServiceWizard.ATTRIBUTE_PROPERTIES, new Properties());
            componentMap.clear();
            activate(context);
        }
    }

    @Override
    public void activate(WizardContext context) {

        super.activate(context);
        JdbcService service = (JdbcService) context.getAttribute(ServiceWizard.ATTRIBUTE_SERVICE);
        if (service == null) {
            return;
        }
        Properties environment = (Properties) context.getAttribute(ServiceWizard.ATTRIBUTE_PROPERTIES);
        if (environment == null) {
            environment = service.getEnvironment();
            context.setAttribute(ServiceWizard.ATTRIBUTE_PROPERTIES, environment);
        }
        enableOptions.setSelected(!environment.isEmpty());
        if (enableOptions.isSelected()) {
            if (driverRefrence == null) {
                driverRefrence = service.getDriver();
            }
            try {
                currentOptions = driverRefrence.getPropertyInfo(service.getUrl(), environment);
                propertyChange(currentOptions);
            } catch (SQLException e) {
                context.showErrorDialog(e, "");
            }
        } else {
            uiContainer.removeAll();
            uiContainer.invalidate();
        }
    }

    public boolean isFirst() {

        return false;
    }

    public boolean isLast() {

        return false;
    }

    @Override
    public void init(WizardContext context) {

        super.init(context);
        setTitle(messages.getMessage("DriverPropertyOptions.title"));
        setComment(messages.getMessage("DriverPropertyOptions.tip"));
        setImage(SwingUtilities.loadIconResource("options", 22));

        String tip = null;
        JButton button = null;

        JPanel panel = new JPanel(new BorderLayout());
        JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
        setView(panel);

        enableOptions.setText(messages.format("DriverPropertyOptions.enable-options"));
        enableOptions.addActionListener(this);
        toolbar.add(enableOptions);
        toolbar.add(Box.createHorizontalGlue());

        toolbar.setFloatable(false);
        tip = messages.format("DriverPropertyOptions.refresh-properties.tip");
        button = new JButton(SwingUtilities.loadIconResource("reload", 16));
        button.setActionCommand("refresh");
        button.addActionListener(this);
        button.setToolTipText(tip);
        toolbar.add(button);

        tip = messages.format("DriverPropertyOptions.revert-properties.tip");
        button = new JButton(SwingUtilities.loadIconResource("undo", 16));
        button.setActionCommand("revert");
        button.addActionListener(this);
        button.setToolTipText(tip);
        toolbar.add(button);

        uiContainer = new JPanel(new GridBagLayout());

        panel.add(BorderLayout.CENTER, new JScrollPane(uiContainer));
        panel.add(BorderLayout.NORTH, toolbar);
    }

    public boolean isValid(WizardContext context) {

        if (!enableOptions.isSelected()) {

        } else {
            JdbcService service = (JdbcService) context.getAttribute(ServiceWizard.ATTRIBUTE_SERVICE);
            Properties environment = (Properties) context.getAttribute(ServiceWizard.ATTRIBUTE_PROPERTIES);
            environment.clear();
            Set<Map.Entry<String, JComponent>> entries = componentMap.entrySet();
            for (Map.Entry<String, JComponent> entry : entries) {
                environment.put(entry.getKey(), getPropertyValue(entry.getValue()));
            }
            service.setEnvironment(environment);
        }
        return true;
    }

    private synchronized void propertyChange(DriverPropertyInfo[] newOptions) {

        Object constraint = null;
        uiContainer.removeAll();
        int row = 0;
        for (int i = 0; i < newOptions.length; i++) {
            DriverPropertyInfo dpi = newOptions[i];
            if (JDBC_ENV_PASSWORD.equals(dpi.name) || JDBC_ENV_PRINCIPAL.equals(dpi.name)) {
                // we already have these pre-defined so skip as to not confuse the user //
                continue;
            }

            JComponent component = componentMap.get(dpi.name);
            if (component != null) {
                updateComponent(component, dpi);
            } else {
                component = createNewEditor(dpi);
                componentMap.put(dpi.name, component);
            }
            JLabel label = new JLabel(dpi.name);
            label.setLabelFor(component);
            if (dpi.required) {
                label.setIcon(SwingUtilities.loadIconResource("button_ok", 16));
            }
            label.setToolTipText(dpi.description);
            constraint = ServiceWizard.constrain(0, row, 1, 1, 0.0, 0.0, WEST, NONE);
            uiContainer.add(label, constraint);
            component.setToolTipText(dpi.description);
            constraint = ServiceWizard.constrain(1, row, 1, 1, 0.0, 1.0, WEST, HORIZONTAL);
            uiContainer.add(component, constraint);
            row++;
        }
        uiContainer.validate();
    }

    private JComponent createNewEditor(DriverPropertyInfo dpi) {

        String[] options = dpi.choices;

        if (options == null) {
            JTextField textField = new JTextField(dpi.value);
            return textField;
        }

        if (options.length == 2) {
            boolean isBooleanProperty = true;
            for (int i = 0; i < 2; i++) {
                String boolString = options[i];
                if ("true".equalsIgnoreCase(boolString) || "false".equalsIgnoreCase(boolString)) {
                    continue;
                }
                isBooleanProperty = false;
                break;
            }
            if (isBooleanProperty) {
                JCheckBox checkBox = new JCheckBox();
                checkBox.setSelected(Boolean.valueOf(dpi.value).booleanValue());
                return checkBox;
            }
        }
        JComboBox choices = new JComboBox(options);
        choices.setSelectedItem(dpi.value);
        return choices;
    }

    private void updateComponent(JComponent component, DriverPropertyInfo newOption) {

        if (component instanceof JTextComponent) {
            JTextComponent textField = (JTextComponent) component;
            textField.setText(newOption.value);
        } else if (component instanceof JCheckBox) {
            JCheckBox checkBox = (JCheckBox) component;
            checkBox.setSelected(Boolean.valueOf(newOption.value).booleanValue());
        } else if (component instanceof JComboBox) {
            JComboBox comboBox = (JComboBox) component;
            comboBox.setSelectedItem(newOption.value);
        }
    }

    private String getPropertyValue(JComponent value) {

        if (value instanceof JTextComponent) {
            return ((JTextComponent) value).getText();
        } else if (value instanceof JCheckBox) {
            return Boolean.toString(((JCheckBox) value).isSelected());
        } else if (value instanceof JComboBox) {
            JComboBox comboBox = (JComboBox) value;
            Object selected = comboBox.getSelectedItem();
            return selected == null ? null : selected.toString();
        }
        return null;
    }
}